import { resolve } from 'path'
import { Inject, Injectable, OnModuleInit } from '@nestjs/common'
import * as chokidar from 'chokidar'
import * as glob from 'fast-glob'
import * as fse from 'fs-extra'
import * as mybatisMapper from 'mybatis-mapper'
import { MYBATIS_OPTIONS } from './mybatis.constants'
import { MybatisOptions, MybatisParams, MybatisFormat } from './mybatis.interface'
import { MybatisMapper } from './mybatis.mapper'

@Injectable()
export class MybatisService<M = MybatisMapper> implements OnModuleInit {
  constructor(@Inject(MYBATIS_OPTIONS) private options: MybatisOptions) {}

  onModuleInit() {
    this.loadMapper()
    this.watchMapper()
  }

  /**
   * 获取 SQL 语句
   * @param namespace 命名空间
   * @param sql sql id
   * @param param 参数
   * @param format 格式
   */
  getSql<N extends keyof M & string, S extends M[N] & string>(
    namespace: N,
    sql: S,
    param?: MybatisParams,
    format?: MybatisFormat
  ) {
    return mybatisMapper.getStatement(
      namespace,
      sql,
      param,
      Object.assign({}, this.options.format || {}, format) as any
    )
  }

  /**
   * 加载 Mapper 文件
   */
  private loadMapper() {
    const { cwd, globs = [] } = this.options
    const files = glob.globSync(globs, { cwd, absolute: true })
    mybatisMapper.createMapper(files)
    this.generateDeclaration()
  }

  /**
   * 监听 Mapper 文件
   */
  private watchMapper() {
    const { cwd, watch = true, globs = [] } = this.options
    if (!watch) return

    const watcher = chokidar.watch(globs, { cwd, ignoreInitial: true })
    watcher.on('add', (file) => {
      mybatisMapper.createMapper([resolve(cwd, file)])
      this.generateDeclaration()
    })
    watcher.on('change', (file) => {
      mybatisMapper.createMapper([resolve(cwd, file)])
      this.generateDeclaration()
    })
    watcher.on('unlink', (file) => {
      // TODO: only clean deleted
      const mapper = mybatisMapper.getMapper()
      Object.keys(mapper).forEach((key) => {
        delete mapper[key]
      })

      this.loadMapper()
    })
  }

  /**
   * 生成 Mapper 声明
   */
  private generateDeclaration() {
    const { cwd, dts = true } = this.options
    if (!dts) return
    if (!cwd && typeof dts === 'boolean') return

    const mapper = mybatisMapper.getMapper()
    const code = `// Do not manually change, generated by @vivy-common/mybatis
// @ts-nocheck
/* eslint-disable */
/* prettier-ignore */
declare module '@vivy-common/mybatis' {
  interface MybatisMapper {
${Object.keys(mapper)
  .map((key) => {
    const value = Object.keys(mapper[key])
    return `    '${key}': ${value.length ? value.map((v) => `'${v}'`).join(' | ') : 'string'}`
  })
  .join('\n')}
  }
}

export {}
`

    const file = typeof dts === 'string' ? dts : resolve(cwd, '../types/mapper.d.ts')
    const prev = fse.pathExistsSync(file) && fse.readFileSync(file, 'utf-8')
    if (code !== prev) {
      fse.outputFileSync(file, code, 'utf-8')
    }
  }
}
